package org.gavin.core.advice;

import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.gavin.core.annotation.AccessLimit;
import org.gavin.core.annotation.Safety;
import org.gavin.core.conf.SafetyFactory;
import org.gavin.core.constant.CommonConst;
import org.gavin.core.enums.CommonStatus;
import org.gavin.core.enums.SafetyType;
import org.gavin.core.exception.ParamException;
import org.gavin.core.service.safety.SafetyAbstract;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/**
 * SafetyRequestBodyAdvice
 *
 * @author grl
 * @date 2023/12/28
 */
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class SafetyRequestBodyAdvice implements RequestBodyAdvice {

    private final HttpServletRequest request;

    private final SafetyFactory safetyFactory;

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        Safety process = methodParameter.getMethodAnnotation(Safety.class);
        return process != null && process.decode();
    }

    /**
     * 约定，请求方解密算法：
     * 获取源字符串
     *
     * @param httpInputMessage
     * @param methodParameter
     * @param type
     * @param aClass
     * @return
     * @throws IOException
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        HttpHeaders headers = httpInputMessage.getHeaders();
        String bodyStr = StreamUtils.copyToString(httpInputMessage.getBody(), StandardCharsets.UTF_8);
        String decSecret = getDecSecret(bodyStr);
        decSecret = StringUtils.isNotBlank(decSecret) ? decSecret : bodyStr;
        return new MyHttpInputMessage(headers, decSecret.getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {

        return o;
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    /**
     * 自定义消息体，因为org.springframework.http.HttpInputMessage#getBody()只能调一次，所以要重新封装一个可重复读的消息体
     */
    @AllArgsConstructor
    public static class MyHttpInputMessage implements HttpInputMessage {

        private HttpHeaders headers;

        private byte[] body;

        @Override
        public InputStream getBody() throws IOException {
            return new ByteArrayInputStream(body);
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }

    private String getDecSecret(String bodyStr) {
        if (StringUtils.isBlank(bodyStr)) {
            return null;
        }
        String type = request.getHeader(CommonConst.SAFETY_TYPE);
        checkHeader(type);
        return getSafetyAbstract(type).getDecrypt(bodyStr);
    }

    private SafetyAbstract getSafetyAbstract(String encryptType) {
        SafetyType safetyType = SafetyType.valueOf(encryptType);
        return switch (safetyType) {
            case SM4, APP -> safetyFactory.getSafety(safetyType);
            default -> throw ParamException.le(CommonStatus.ENCRYPTION_NOT_YET_ACTIVATED, "safetyRequest", CommonConst.SAFETY_TYPE);
        };
    }

    private void checkHeader(String type) {
        if (StringUtils.isBlank(type)) {
            throw ParamException.le(CommonStatus.LACK_HEADER_PARAM_ERROR, "safetyRequest", CommonConst.SAFETY_TYPE);
        }
    }
}
